package org.json.sax;


import java.io.InputStream;
import java.util.Vector;

import org.json.util.JSONPath;

/**
 * JSON SAX-like parser for Java ME
 * 
 * @author diegoyam
 */
public class JSONParser {

	private static final short UNKNOWN = -5;
	private static final short FIND_KEY = -4;
	private static final short FIND_TYPE = -3;
	private static final short FIND_SEPARATOR_OR_END = -2;
	private static final short JSON_SIMPLE = -1;
	private static final short JSON_OBJECT = 0;
	private static final short JSON_ARRAY = 1;

	private static final char[] CLOSING_CHARS = { '}', ']' };
	
	private final JSONTokenizer tokenizer;
	private MarkerStack stack;
	private short parsingType;
	private String key;
	private JSONPath path;
	private boolean finished, canceled;
	
	public JSONParser(String json) {
		tokenizer = new JSONStringTokenizer(json);
		parsingType = UNKNOWN;
		stack = new MarkerStack();
		path  = new JSONPath();
		finished = false;
	}
	
	public JSONParser(InputStream jsonStream) {
		tokenizer = new JSONStreamTokenizer(jsonStream);
		parsingType = UNKNOWN;
		stack = new MarkerStack();
		path  = new JSONPath();
		finished = false;
	}
	
	public JSONParser(JSONMarker marker) {
		parsingType = marker.type;
		tokenizer = new JSONStringTokenizer(marker.tokenizer,
				marker.tokenizerIndex);
		stack = new MarkerStack();
		stack.push(marker);
		this.path = new JSONPath();
		finished = false;
	}

	public boolean isFinished() {
		return finished;
	}
	
	public boolean isCanceled() {
		return canceled;
	}

	public void cancel() {
		this.canceled = true;
	}
	
	public JSONPath getCurrentPath() {
		return path;
	}

	public JSONMarker getJSONMarker() {
		return (stack == null) ? null: stack.top();
	}

	public void parse(JSONParsingEventHandler eventHandler) throws JSONParsingException {
		
		try {
			if (finished) {
				if (eventHandler != null)
					eventHandler.handleEndParsing();
				return;
			}
			
			char c = 0;
			if (parsingType == UNKNOWN) {
				c = tokenizer.nextClean();
				if ( c != '{')
					throw tokenizer.syntaxError("A JSONObject text must begin with '{'");
				stack.push(new JSONMarker(JSON_OBJECT, tokenizer));
				parsingType = FIND_KEY;
			}
			
			for(;;) {
				
				if (parsingType == JSON_OBJECT) { // consumes '{' and ...
					parsingType = FIND_KEY;
					continue;
				}
				
				if (parsingType == JSON_ARRAY) { // consumes '[' and ...
					parsingType = FIND_TYPE;
					continue;
				}
				
				if (parsingType == JSON_SIMPLE) { // simple types
					Object v = tokenizer.nextValue();
	
					if (eventHandler != null)
						eventHandler.handleValue(v, path);
	
					path.pop();
					parsingType = FIND_SEPARATOR_OR_END;
					return;
				}
				
				c = tokenizer.nextClean();
				if (c == 0) {
					break;
				}
				
				if (c == '}' || c == ']') {
					checkClosingChar(c);
					JSONMarker m = stack.top();
					stack.pop();
					if (stack.isEmpty()) 
						break;
					if (eventHandler != null) {
						if (m.type == JSON_OBJECT)
							eventHandler.handleEndObject(path);
						else eventHandler.handleEndArray(path); 
					}
					path.pop();
					parsingType = FIND_SEPARATOR_OR_END;
					return;
				}
				
				if (parsingType == FIND_SEPARATOR_OR_END) {
					if (c != ',') {
						try {
							checkClosingChar(c);
						} catch (JSONParsingException e) {
							throw tokenizer.syntaxError("Expected ',' or '" 
									+ CLOSING_CHARS[stack.top().type] + "'");
						}
						tokenizer.back();
					}
					// separator found, find next obj now
					if (stack.top().type == JSON_ARRAY)
						parsingType = FIND_TYPE;
					else parsingType = FIND_KEY;
					continue;
				}
				
				if (parsingType == FIND_KEY) {
					if (c == '"' || c == '\'') {
						key = tokenizer.nextString(c);
						c = tokenizer.nextClean();
						if (c != ':')
							throw tokenizer
									.syntaxError("Expected a ':' after a key");
						path.push(key);
						parsingType = FIND_TYPE;
						continue;
					}
					throw tokenizer.syntaxError("Expected new KEY");
				}
	
				if (parsingType == FIND_TYPE) {
	
					if (stack.isArrayOnTop())		// Coming from JSON_ARRAY ?
						path.push(stack.top().arrayIndex());
					stack.top().incArrayIndex();	// increments the size of stack.top() element
	
					// discovering type
					if (c == '[') {
						parsingType = JSON_ARRAY;
						stack.push(new JSONMarker(parsingType, tokenizer));
					} else if (c == '{') {
						parsingType = JSON_OBJECT;
						stack.push(new JSONMarker(parsingType, tokenizer));
					} else {
						parsingType = JSON_SIMPLE;
						tokenizer.back();	// just identifies the type
					}				
					
					// notify evt handler "keyFound" event
					if (eventHandler != null)
						if (parsingType == JSON_OBJECT) eventHandler.handleObjectFound(path);
						else if (parsingType == JSON_ARRAY) eventHandler.handleArrayFound(path);
					return;
				}
				throw tokenizer.syntaxError("Malformed JSON");
			}
			
			if (!stack.isEmpty())
				throw tokenizer.syntaxError("A JSONObject text must end with '}'");
			
			finished = true;
			if (eventHandler != null)
				eventHandler.handleEndParsing();
			
		} catch (JSONParsingException j) {
			if (!(j.code == JSONParsingException.CANCELED))
				throw j;
		} catch (ClassCastException e) {
			throw tokenizer
					.syntaxError("ClassCastException when parsing, propably handling value for key '"
							+ path + "'");
		}
	}

	/**
	 * Checks if a char `c' is a valid closing char for
	 * the element being parsed
	 * 
	 * @param c
	 * @throws JSONParsingException
	 */
	private void checkClosingChar(char c) throws JSONParsingException {
		boolean invalid;

		invalid = stack.isEmpty() || c != CLOSING_CHARS[stack.top().type];
		if (invalid) {
			throw tokenizer.syntaxError("Expected char '"
					+ CLOSING_CHARS[stack.top().type] + "'");
		}
	}

	/***********************************************************************************************
	 ***********************************************************************************************/
	
	/**
	 * Interface that should be used to receive Parse events
	 *   
	 * @author diegoyam
	 */
	public static interface JSONParsingEventHandler {
		
		void handleObjectFound(JSONPath path) throws JSONParsingException;

		void handleArrayFound(JSONPath path) throws JSONParsingException;
		
		void handleValue(Object value, JSONPath path) throws JSONParsingException;

		void handleEndObject(JSONPath path) throws JSONParsingException;
		
		void handleEndArray(JSONPath path) throws JSONParsingException;

		void handleEndParsing() throws JSONParsingException;

		boolean isFinished();

		void reset();
	}

	/**
	 * Internal utility class that implement a Stack basic functionality
	 * Initially implemented as a Vector, depending on later performance tests
	 * can be enhanced to use an optimized array..
	 *   
	 * @author diegoyam
	 */
	class MarkerStack extends Vector {
		
		public MarkerStack() {
			super(32);
		}
		
		public boolean isArrayOnTop() {
			return !isEmpty() && top().type == JSON_ARRAY;
		}

		public void push(JSONMarker o) {
			addElement(o);
		}
		
		public JSONMarker pop() {
			if (size() < 1)
				return null;
			Object r = lastElement();
			removeElementAt(size()-1);
			return (JSONMarker)r;
		}

		public JSONMarker top() { 
			return (JSONMarker)lastElement();
		}
//		public JSONMarker top() { 
//			return (JSONMarker)peek();
//		}
	}


	/**
	 * Class used to represent some Point in the JSON
	 * being parsed.
	 * It can be used
	 * @author diegoyam
	 *
	 */
	public static class JSONMarker {
		
		short type;
		private int iArray;
		private int tokenizerIndex;
		private JSONTokenizer tokenizer;
		
		public JSONMarker(short type, JSONTokenizer tokenizer) {
			this.type	= type;
			this.iArray = 0;
			this.tokenizer = tokenizer;
			this.tokenizerIndex = tokenizer.getIndex();
		}

		public int incArrayIndex() {
			return iArray++;
		}
		
		public int arrayIndex() {
			return iArray;
		}
		
//		public JSONParser getNewParser() {
//			JSONTokenizer jt = new JSONTokenizer(tokenizer, tokenizerIndex);
//			return new JSONParser(type, jt);
//		}
	}

	/**
	 * The JSONException is thrown by the JSON.org classes then things are amiss.
	 * @author JSON.org
	 * @version 2
	 */
	public static class JSONParsingException extends Exception {
	    
		public static final byte CANCELED = 1;
		public static final byte SYNTAX_ERROR = 2;
		public static final byte IO_EXCEPTION = 3;
		
	    private Throwable cause;
	    byte code;
	    
	    /**
	     * Constructs a JSONException with an explanatory message.
	     * @param message Detail about the reason for the exception.
	     */
	    public JSONParsingException(String message) {
	        super(message);
			this.code = SYNTAX_ERROR;
	    }

	    public JSONParsingException(Throwable t) {
	        super(t.getMessage());
	        this.cause = t;
			this.code = SYNTAX_ERROR;
	    }

	    public JSONParsingException(byte code) {
			this.code = code;
		}

		public Throwable getCause() {
	        return this.cause;
	    }
	}

//	private static void debug(String string) {
//		System.err.print("[PARSER] ");
//		System.err.println(string);
//	}

//	private String strType(int parsingType2) {
//		switch (parsingType2) {
//		case JSON_OBJECT:
//			return "Object";
//		case JSON_ARRAY:
//			return "Array";
//		case JSON_SIMPLE:
//			return "Simple";
//		default:
//			return "Unknown";
//		}
//	}
}
